package com.qing.simple_player.player;

import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.session.MediaSession;

import com.qing.simple_player.util.AssertUtil;
import com.qing.simple_player.util.DebugLog;

import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * 默认实现一些播放器的基础功能，如播放、暂停、停止、播放进度、播放状态等。
 * 使用 ExoPlayer 为播放器基础实现，需要主动管理音频焦点
 */
public class BasePlayerImpl implements IPlayer {

    private static final int MSG_PLAY = 0x01;
    private static final int MSG_PAUSE = 0x02;

    private static final int MSG_STOP = 0x03;

    private static final int MSG_SEEK_TO = 0x04;
    private static final int MSG_SET_PLAYBACK_SPEED = 0x06;
    private static final int MSG_RELEASE = 0x07;
    private static final int MSG_SET_MEDIA_ITEMS = 0x08;
    private static final int MSG_SET_PLAY_WHEN_READY = 0x09;

    protected static final boolean DEBUG_HANDLE = true;

    private final Player mPlayer;

    private MediaSession mMediaSession = null;

    private final String TAG;
    /**
     * media session id，需要保证唯一性
     */
    private final String SESSION_ID;

    private final H mHandler;

    private int mPendingPlaybackState = PLAY_STATE_IDLE;

    private final Listener mListener;

    protected static final long UPDATE_PLAYBACK_PROGRESS_INTERVAL = 50L;

    private long mCurrentPosition = 0L;
    private long mDuration = 0L;

    private final Runnable mUpdateProgressRunnable = new Runnable() {
        @Override
        public void run() {
            if (!mPlayer.isPlaying()) return;
            updateProgress();

            mHandler.postDelayed(this, UPDATE_PLAYBACK_PROGRESS_INTERVAL);
        }
    };

    private final Player.Listener mInitListener = new Player.Listener() {
        @Override
        public void onPlaybackStateChanged(int playbackState) {
            DebugLog.d(TAG, "onPlaybackStateChanged: playbackState=" + IPlayer.playStateToString(playbackState));
            if (mListener != null) {
                mListener.onPlaybackStateChanged(playbackState);
            }
            if (mPendingPlaybackState != PLAY_STATE_SEEKING) {
                setPlaybackState(playbackState);
            }
            if (playbackState == PLAY_STATE_READY) {
                mHandler.post(BasePlayerImpl.this::updateProgress);
            } else if (playbackState == PLAY_STATE_ENDED) {
                mCurrentPosition = 0L;
                if (mListener != null) {
                    mListener.onProgressChanged(mCurrentPosition, mDuration);
                }
            }
        }

        @Override
        public void onPlayerError(@NonNull PlaybackException error) {
            DebugLog.d(TAG, "onPlayerError: ", error);
            if (mListener != null) {
                mListener.onPlayerError(error);
            }
            setPlaybackState(PLAY_STATE_ERROR);
        }

        @Override
        public void onIsPlayingChanged(boolean isPlaying) {
            if (mListener != null) {
                mListener.onIsPlayingChanged(isPlaying);
            }
            if (isPlaying) {
                setPlaybackState(PLAY_STATE_PLAYING);
                mHandler.post(mUpdateProgressRunnable);
            } else {
                // 结束和跳转时不需要设置播放状态为暂停
                if (mPendingPlaybackState != PLAY_STATE_ENDED && mPendingPlaybackState != PLAY_STATE_SEEKING) {
                    setPlaybackState(PLAY_STATE_PAUSED);
                }
                mHandler.removeCallbacks(mUpdateProgressRunnable);
            }
        }

        @Override
        public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
            DebugLog.d(TAG, String.format(Locale.getDefault(), "onPositionDiscontinuity: old=%s -> new=%s, reason=%d", oldPosition.positionMs, newPosition.positionMs, reason));
            if (mListener != null) {
                mListener.onPositionDiscontinuity(oldPosition, newPosition, reason);
            }
            mCurrentPosition = newPosition.positionMs;
            if (mListener != null) {
                mListener.onProgressChanged(mCurrentPosition, mDuration);
            }
            if (reason == Player.DISCONTINUITY_REASON_SEEK) {
                setPlaybackState(PLAY_STATE_SEEKING);
            }
        }
    };

    /**
     * 播放器构造方法
     *
     * @param context     上下文
     * @param bindSession 是否绑定媒体会话，传入true则自动创建媒体会话，并绑定到播放器，调用{@link #getMediaSession()}获取媒体会话
     * @param listener    播放器状态监听器
     * @param tag         播放器标识，用于日志打印
     */
    public BasePlayerImpl(Context context, boolean bindSession, @Nullable Listener listener, String tag, Intent sessionIntent) {
        mPlayer = new ExoPlayer.Builder(context).build();
        mListener = listener;
        mPlayer.addListener(mInitListener);
        TAG = "BasePlayerImpl" + tag;
        SESSION_ID = TAG + "@" + Integer.toHexString(hashCode());
        mHandler = new H(this, Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper());
        DebugLog.d(TAG, "BasePlayerImpl: tag=" + tag + ", bindSession=" + bindSession + ", sessionId=" + SESSION_ID);
        DebugLog.d(TAG, "BasePlayerImpl: sessionIntent=" + sessionIntent);
        if (bindSession) {
            mMediaSession = new MediaSession.Builder(context, mPlayer)
                    .setId(SESSION_ID)
                    .build();
        }
    }

    @Override
    public void setMediaItem(@NonNull MediaItem mediaItem) {
        setMediaItems(mediaItem);
    }

    @Override
    public void setMediaUrl(@NonNull String url) {
        DebugLog.d(TAG, "setMediaItem: url=" + url);
        setMediaItem(MediaItem.fromUri(url));
    }

    @Override
    public void setMediaItems(@NonNull MediaItem... mediaItems) {
        mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_MEDIA_ITEMS, Arrays.asList(mediaItems)));
    }

    @Override
    public void setMediaUrls(@NonNull String... urls) {
        DebugLog.d(TAG, "setMediaItems: urls=" + Arrays.toString(urls));
        setMediaItems(Arrays.stream(urls).map(MediaItem::fromUri).toArray(MediaItem[]::new));
    }

    @Override
    public void play() {
        DebugLog.d(TAG, "play");
        mHandler.sendEmptyMessage(MSG_PLAY);
    }

    @Override
    public void pause() {
        DebugLog.d(TAG, "pause");
        mHandler.sendEmptyMessage(MSG_PAUSE);
    }

    @Override
    public void seekTo(long positionMs) {
        DebugLog.d(TAG, "seekTo: " + positionMs);
        mHandler.sendMessage(mHandler.obtainMessage(MSG_SEEK_TO, positionMs));
    }

    @Override
    public void stop() {
        DebugLog.d(TAG, "stop");
        mHandler.sendEmptyMessage(MSG_STOP);
    }

    @Override
    public void setPlaybackSpeed(float speed) {
        DebugLog.d(TAG, "setPlaybackSpeed: " + speed);
        mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_PLAYBACK_SPEED, speed));
    }

    @Override
    public void setPlayWhenReady(boolean playWhenReady) {
        DebugLog.d(TAG, "setPlayWhenReady: " + playWhenReady);
        mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady));
    }

    @Override
    public long getCurrentPosition() {
        return mCurrentPosition;
    }

    @Override
    public long getDuration() {
        return mDuration;
    }

    @Override
    public void release() {
        DebugLog.d(TAG, "release");
        mHandler.sendEmptyMessage(MSG_RELEASE);
    }

    @Nullable
    @Override
    public MediaSession getMediaSession() {
        return mMediaSession;
    }

    @CallSuper
    protected void handlePlay() {
        mPlayer.play();
    }

    @CallSuper
    protected void handleSetMediaItems(@NonNull List<MediaItem> mediaItems) {
        handleSetPlayWhenReady(false);
        if (DEBUG_HANDLE) {
            DebugLog.d(TAG, "handleSetMediaItems: mediaItems=" + mediaItems.size());
        }
        mPlayer.setMediaItems(mediaItems);
        mPlayer.prepare();
    }

    @CallSuper
    protected void handleSetPlayWhenReady(boolean playWhenReady) {
        if (DEBUG_HANDLE) {
            DebugLog.d(TAG, "handleSetPlayWhenReady: playWhenReady=" + playWhenReady);
        }
        mPlayer.setPlayWhenReady(playWhenReady);
    }

    @CallSuper
    protected void handleSeekTo(long positionMs) {
        if (DEBUG_HANDLE) {
            DebugLog.d(TAG, "handleSeekTo: positionMs=" + positionMs);
        }
        mPlayer.seekTo(positionMs);
    }

    @CallSuper
    protected void handleStop() {
        if (DEBUG_HANDLE) {
            DebugLog.d(TAG, "handleStop");
        }
        mPlayer.stop();
    }

    @CallSuper
    protected void handleSetPlaybackSpeed(float speed) {
        if (DEBUG_HANDLE) {
            DebugLog.d(TAG, "handleSetPlaybackSpeed: speed=" + speed);
        }
        mPlayer.setPlaybackSpeed(speed);
    }

    @CallSuper
    protected void handleRelease() {
        if (DEBUG_HANDLE) {
            DebugLog.d(TAG, "handleRelease");
        }
        if (mMediaSession != null) {
            mMediaSession.release();
            mMediaSession = null;
        }
        mPlayer.release();
        mPlayer.removeListener(mInitListener);
    }

    protected void updateProgress() {
        AssertUtil.checkState(isApplicationThread(), "Expected method call on application thread");
        mCurrentPosition = mPlayer.getCurrentPosition();
        mDuration = mPlayer.getDuration();
        if (mListener != null) {
            mListener.onProgressChanged(mCurrentPosition, mDuration);
        }
    }

    protected boolean isApplicationThread() {
        return Thread.currentThread() == mHandler.getLooper().getThread();
    }

    private void setPlaybackState(@PlaySate int state) {
        DebugLog.d(TAG, "setPlaybackState: 设置播放状态 " + IPlayer.playStateToString(mPendingPlaybackState) + " ==> " + IPlayer.playStateToString(state));
        if (mPendingPlaybackState != state) {
            mPendingPlaybackState = state;
        }
        if (mListener != null) {
            mListener.onPlayStateChanged(state);
        }
    }

    @NonNull
    private static String msgToString(int msg) {
        switch (msg) {
            case MSG_PLAY:
                return "MSG_PLAY";
            case MSG_PAUSE:
                return "MSG_PAUSE";
            case MSG_STOP:
                return "MSG_STOP";
            case MSG_SEEK_TO:
                return "MSG_SEEK_TO";
            case MSG_SET_PLAYBACK_SPEED:
                return "MSG_SET_PLAYBACK_SPEED";
            case MSG_RELEASE:
                return "MSG_RELEASE";
            case MSG_SET_MEDIA_ITEMS:
                return "MSG_SET_MEDIA_ITEMS";
            case MSG_SET_PLAY_WHEN_READY:
                return "MSG_SET_PLAY_WHEN_READY";
            default:
                return "UNKNOWN";
        }
    }

    protected static class H extends Handler {

        private final WeakReference<BasePlayerImpl> mPlayerRef;

        public H(BasePlayerImpl player, Looper looper) {
            super(looper);
            mPlayerRef = new WeakReference<>(player);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            DebugLog.d(mPlayerRef.get().TAG, "handleMessage: msg= [" + msgToString(msg.what) + "]");
            switch (msg.what) {
                case MSG_PLAY:
                    mPlayerRef.get().handlePlay();
                    break;
                case MSG_PAUSE:
                    mPlayerRef.get().handleSetPlayWhenReady(false);
                    break;
                case MSG_STOP:
                    mPlayerRef.get().handleStop();
                    break;
                case MSG_SEEK_TO:
                    mPlayerRef.get().handleSeekTo((long) msg.obj);
                    break;
                case MSG_SET_PLAYBACK_SPEED:
                    mPlayerRef.get().handleSetPlaybackSpeed((float) msg.obj);
                    break;
                case MSG_RELEASE:
                    mPlayerRef.get().handleRelease();
                    break;
                case MSG_SET_MEDIA_ITEMS:
                    if (msg.obj instanceof List) {
                        mPlayerRef.get().handleSetMediaItems((List<MediaItem>) msg.obj);
                    }
                    break;
                case MSG_SET_PLAY_WHEN_READY:
                    mPlayerRef.get().handleSetPlayWhenReady((boolean) msg.obj);
                    break;
            }
        }
    }
}
